#include "tracer.h"

#include <QtWidgets/QtWidgets>

#include "config-fnt.h"
#include "session.h"
#include "splash.h"
#include "load.thread.h"
#include "cache.thread.h"
#include "compute.thread.h"
#include "viewer.h"
#include "options.dlg.h"
#include "dialogs.h"
#include "plugin.manager.h"
#include "options.h"
#include "misc.h"
#include <chrono>
#include <fstream>

#ifdef DEBUG_EVENT
#include <fstream>

	std::ofstream ofs;
#endif

struct TracerPriv {
	Tracer* tracer;
	TracerState state;

	LoadThreadShared* loaderShared;
	ViewerShared* viewerShared;


	Options options;

	std::array<QAction*, TRACER_ACTION_NUM> actions;

	AboutDlg* aboutDlg;

	QList<Session*> sessions_not_ready;
	QList<Session*> sessions;

	QMenu* recentMenu;
	QList<QAction*> recentFiles;
	QActionGroup* recentGroup;
	bool recentChanged;

	QList<QString> swapFiles;
	bool swapChanged;

	QList<QString> toCreate;
	QList<QString> toRecover;
	QList<QString> toOpen;

	std::ofstream logfile;

	TracerPriv(Tracer* t):
		tracer{t}, state{TracerState::Invalid},
		loaderShared{nullptr}, viewerShared{nullptr},
		options{},
		actions{nullptr,},
		aboutDlg{nullptr},
		sessions_not_ready{}, sessions{},
		recentFiles{}, recentChanged{false},
		swapFiles{}, swapChanged{false}
	{
		loaderShared=LoadThread::createShared();
		viewerShared=Viewer::createShared();
#ifdef DEBUG_EVENT
		ofs.open("/tmp/fnt.debug.log");
#endif
	}
	~TracerPriv() {
#ifdef DEBUG_EVENT
		ofs.close();
#endif
		if(loaderShared)
			LoadThread::destroyShared(loaderShared);
		if(viewerShared)
			Viewer::destroyShared(viewerShared);
	}

	void loadOptions() {
		QFile f{pathOfFnt()+"/config"};
		if(!f.open(QIODevice::ReadOnly))
			return;
		QTextStream fs{&f};
		auto line=fs.readLine();
		while(!line.isNull()) {
			options.putLine(line);
			line=fs.readLine();
		}
	}
	void saveOptions() {
		if(options.isChanged()) {
			try {
				QFile f{pathOfFnt()+"/config"};
				if(!f.open(QIODevice::WriteOnly)) {
					throwError("Failed to write configurations!\n");
				}
				QTextStream fs{&f};
				auto lines=options.getLines();
				for(auto& line: lines) {
					fs<<line<<'\n';
					if(fs.status()!=QTextStream::Ok) {
						throwError("Failed to write line");
					}
				}
			} catch(const std::exception& e) {
				showWarning("Failed to save configurations", e.what(), nullptr);
			}
		}
	}

	void recentInit() {
		auto files=options.getStringList("recent.files");
		recentMenu=new QMenu{"Recent", nullptr};
		recentMenu->setIcon(QIcon{":/images/breeze/document-open-recent.svg"});
		recentGroup=new QActionGroup{tracer};
		for(auto& f: files) {
			auto act=new QAction{f, recentGroup};
			act->setData(f);
			recentMenu->insertAction(nullptr, act);
			recentFiles.push_back(act);
			recentGroup->addAction(act);
			QObject::connect(act, &QAction::triggered, tracer, &Tracer::openSessionRecent);
		}

		recentMenu->addSeparator();
		auto actClear=new QAction{QIcon{":/images/breeze/edit-clear.svg"}, "&Clear", tracer};
		recentMenu->insertAction(nullptr, actClear);
		QObject::connect(actClear, &QAction::triggered, tracer, &Tracer::clearRecentList);

		recentMenu->setEnabled(false);
	}
	void recentDeinit() {
		if(recentChanged) {
			QStringList files;
			for(auto f: recentFiles)
				files<<f->data().toString();
			options.setStringList("recent.files", files);
		}
		delete recentMenu;
	}

	void setupActions() {
		actions[SESSION_NEW]=new QAction{QIcon{":/images/breeze/document-new.svg"}, "&New...", tracer};
		actions[SESSION_NEW]->setToolTip("Create a session from local or remote imaging data.");
		actions[SESSION_NEW]->setShortcut(QKeySequence{"Ctrl+N"});
		QObject::connect(actions[SESSION_NEW], &QAction::triggered, tracer, &Tracer::createSession);

		actions[SESSION_OPEN]=new QAction{QIcon{":/images/breeze/document-open.svg"}, "&Open...", tracer};
		actions[SESSION_OPEN]->setToolTip("Open a previously saved session.");
		actions[SESSION_OPEN]->setShortcut(QKeySequence{"Ctrl+O"});
		QObject::connect(actions[SESSION_OPEN], &QAction::triggered, tracer, &Tracer::openSession);

		actions[TRACER_SETTINGS]=new QAction{QIcon{":/images/breeze/configure.svg"}, "&Options...", tracer};
		actions[TRACER_SETTINGS]->setToolTip("Show the Options dialog to change various settings.");
		actions[TRACER_SETTINGS]->setShortcut(QKeySequence{""});
		QObject::connect(actions[TRACER_SETTINGS], &QAction::triggered, tracer, &Tracer::showSettings);

		actions[TRACER_QUIT]=new QAction{QIcon{":/images/breeze/application-exit.svg"}, "&Quit", tracer};
		actions[TRACER_QUIT]->setToolTip("Quit this application.");
		actions[TRACER_QUIT]->setShortcut(QKeySequence{"Ctrl+Q"});
		QObject::connect(actions[TRACER_QUIT], &QAction::triggered, tracer, &Tracer::quitTracer, Qt::QueuedConnection);

		actions[TRACER_MANUAL]=new QAction{QIcon{":/images/breeze/help-contents.svg"}, "&Manual...", tracer};
		actions[TRACER_MANUAL]->setToolTip("Show manual for this application.");
		actions[TRACER_MANUAL]->setShortcut(QKeySequence{"F1"});
		QObject::connect(actions[TRACER_MANUAL], &QAction::triggered, tracer, &Tracer::showManual);

		actions[TRACER_ABOUT]=new QAction{QIcon{":/images/breeze/help-about.svg"}, "&About...", tracer};
		actions[TRACER_ABOUT]->setToolTip("Show application information.");
		actions[TRACER_ABOUT]->setShortcut(QKeySequence{"Shift+F1"});
		QObject::connect(actions[TRACER_ABOUT], &QAction::triggered, tracer, &Tracer::showAbout);

		for(int i=0; i<TRACER_ACTION_NUM; i++) {
			//actions[i]->setShortcutContext(false);
			actions[i]->setEnabled(false);
		}
	}

	void recentMenuClear() {
		/*
		for(auto i=recents.begin(); i!=recents.end(); ) {
			if((*i)->isEnabled()) {
				delete *i;
				auto j=i;
				++i;
				recents.erase(j);
				recentsChanged=true;
			} else {
				++i;
			}
		}
		*/
	}
	/*
		void TracerRecent::open(const QString& ff) {
		auto f=QDir{ff}.canonicalPath();
		int p=-1;
		for(int i=0; i<recents.size(); i++) {
		if(recents[i]->data().toString()==f) {
		p=i;
		break;
		}
		}
		if(p==-1) {
		auto act=new QAction{f, tracer};
		act->setData(f);
		auto par=tracer;
		QObject::connect(act, &QAction::triggered, [par, f]() { par->openSessionRecent(f); });
		recents.prepend(act);
		recentsChanged=true;
		} else if(p==0) {
		recentsMenu->removeAction(recents[0]);
		} else {
		recentsMenu->removeAction(recents[p]);
		auto v=recents.takeAt(p);
		recents.prepend(v);
		recentsChanged=true;
		}
		}
		void TracerRecent::saveas(const QString& pre, const QString& post) {
		if(pre==post)
		return;
		close(pre);
		open(post);
		}
		void TracerRecent::close(const QString& ff) {
		auto f=QDir{ff}.canonicalPath();
		if(recents.size()>0 && recents[0]->data().toString()==f) {
		if(recents.size()>1) {
		recentsMenu->insertAction(recents[1], recents[0]);
		} else {
		recentsMenu->insertAction(recentsSep, recents[0]);
		}
		}
		}

*/
	bool createSession() {
		switch(state) {
			case TracerState::Invalid:
			case TracerState::Restricted:
				return false;
			case TracerState::Ready:
				break;
		}
		auto par=sessions.empty()?static_cast<QWidget*>(Splash::instance()):sessions.front();

		LoadThreadInput loc{};
		for(int i=0; i<9; i++)
			loc.direction[i]=i/3==i%3?1:0;
		for(int i=0; i<3; i++)
			loc.origin[0]=0;
		if(!CreateSessionDialog::getLocation(par, &loc.location, &loc.origin, &loc.direction))
			return false;

		auto swapfn=pathOfSwapFile(loc.location);
		QFile swapf{swapfn};
		bool recover=false;
		if(swapf.exists()) {
			QMessageBox mbox(QMessageBox::Question, "Recover", "There is an auto-saved version. Recover unsaved content? (Choosing \"No\" will remove it.)", QMessageBox::Yes|QMessageBox::No, par);
			mbox.setDefaultButton(QMessageBox::Yes);
			auto res=mbox.exec();
			if(res==QMessageBox::No) {
				swapf.remove();
			} else {
				recover=true;
			}
		}

		Session* s{nullptr};
		try {
			if(recover) {
				s=new Session{QString{}, swapfn};
			} else {
				s=new Session{loc};
			}
			sessions_not_ready.push_front(s);
			QObject::connect(s, &Session::stateChanged, tracer, &Tracer::sessionStateChanged);
			QObject::connect(s, &Session::requestCloseSession, tracer, &Tracer::sessionRequestClose, Qt::QueuedConnection);
			QObject::connect(s, &Session::sessionReady, tracer, &Tracer::sessionReady);
			s->show();
			//s->showStatus("Session created.", 2000);
		} catch(const std::exception& e) {
			showWarning("Failed to create session", e.what(), par);
			sessions_not_ready.removeAll(s);
			delete s;
			return false;
		}
		return true;
	}

	bool openSession() {
		switch(state) {
			case TracerState::Invalid:
			case TracerState::Restricted:
				return false;
			case TracerState::Ready:
				break;
		}
		auto par=sessions.empty()?static_cast<QWidget*>(Splash::instance()):sessions.front();

		QFileDialog dlg(par, "Please choose a FNT Session file.", "", "");
		dlg.setAcceptMode(QFileDialog::AcceptOpen);
		dlg.setFileMode(QFileDialog::ExistingFile);
		dlg.setNameFilter("FNT Session file (*.fnt *.fntz)");
		if(dlg.exec()!=QDialog::Accepted)
			return false;

		auto f=dlg.selectedFiles().at(0);
		auto swapFileName=pathOfSwapFile(f);
		QFile swapFile{swapFileName};
		bool recover=false;
		if(swapFile.exists()) {
			QMessageBox mbox(QMessageBox::Question, "Recover", "There is an auto-saved      version. Recover unsaved content? (Choosing \"No\" will remove it.)", QMessageBox::Yes|QMessageBox::No, par);
			mbox.setDefaultButton(QMessageBox::Yes);
			auto res=mbox.exec();
			if(res==QMessageBox::No) {
				swapFile.remove();
			} else {
				recover=true;
			}
		}

		Session* s{nullptr};
		try {
			if(recover) {
				s=new Session{f, swapFileName};
				// XXX ui.recentMenu.open(f);
			} else {
				s=new Session{f};
				// XXX ui.recentMenu.open(f);
			}
			sessions_not_ready.push_front(s);
			QObject::connect(s, &Session::stateChanged, tracer, &Tracer::sessionStateChanged);
			QObject::connect(s, &Session::requestCloseSession, tracer, &Tracer::sessionRequestClose, Qt::QueuedConnection);
			QObject::connect(s, &Session::sessionReady, tracer, &Tracer::sessionReady);
			s->show();
			//s->showStatus("Session opened.", 2000);
		} catch(const std::exception& e) {
			showWarning("Failed to open session", e.what(), s);
			sessions_not_ready.removeAll(s);
			delete s;
			return false;
		}
		return true;
	}

	bool openSessionRecent(QObject* sender) {
		switch(state) {
			case TracerState::Invalid:
			case TracerState::Restricted:
				return false;
			case TracerState::Ready:
				break;
		}
		auto par=sessions.empty()?static_cast<QWidget*>(Splash::instance()):sessions.front();

		auto a=qobject_cast<QAction*>(sender);
		if(!a)
			return false;
		auto f=a->data().toString();
		if(!f.isEmpty())
			return false;

		auto swapFileName=pathOfSwapFile(f);
		QFile swapFile{swapFileName};
		bool recover=false;
		if(swapFile.exists()) {
			QMessageBox mbox(QMessageBox::Question, "Recover", "There is an auto-saved      version. Recover unsaved content? (Choosing \"No\" will remove it.)", QMessageBox::Yes|QMessageBox::No, par);
			mbox.setDefaultButton(QMessageBox::Yes);
			auto res=mbox.exec();
			if(res==QMessageBox::No) {
				swapFile.remove();
			} else {
				recover=true;
			}
		}

		Session* s{nullptr};
		try {
			if(recover) {
				s=new Session{f, swapFileName};
				// XXX ui.recentMenu.open(f);
			} else {
				s=new Session{f};
				// XXX ui.recentMenu.open(f);
			}
			sessions_not_ready.push_front(s);
			QObject::connect(s, &Session::stateChanged, tracer, &Tracer::sessionStateChanged);
			QObject::connect(s, &Session::requestCloseSession, tracer, &Tracer::sessionRequestClose, Qt::QueuedConnection);
			QObject::connect(s, &Session::sessionReady, tracer, &Tracer::sessionReady);
			s->show();
			//s->showStatus("Session opened.", 2000);
		} catch(const std::exception& e) {
			showWarning("Failed to open session", e.what(), s);
			sessions_not_ready.removeAll(s);
			delete s;
			return false;
		}
		return true;
	}

	bool openSessionRecover(QObject* sender) {
		switch(state) {
			case TracerState::Invalid:
			case TracerState::Restricted:
				return false;
			case TracerState::Ready:
				break;
		}
		//auto par=sessions.empty()?static_cast<QWidget*>(Splash::instance()):sessions.front();

		auto a=qobject_cast<QAction*>(sender);
		if(!a)
			return false;
		auto f=a->data().toString();
		if(!f.isEmpty())
			return false;

		auto swapFileName=pathOfSwapFile(f);
		Session* s{nullptr};
		try {
			s=new Session{f, swapFileName};
			// XXX ui.recentMenu.open(f);
			sessions_not_ready.push_front(s);
			QObject::connect(s, &Session::stateChanged, tracer, &Tracer::sessionStateChanged);
			QObject::connect(s, &Session::requestCloseSession, tracer, &Tracer::sessionRequestClose, Qt::QueuedConnection);
			QObject::connect(s, &Session::sessionReady, tracer, &Tracer::sessionReady);
			s->show();
			//s->showStatus("Session opened.", 2000);
		} catch(const std::exception& e) {
			showWarning("Failed to open session", e.what(), s);
			sessions_not_ready.removeAll(s);
			delete s;
			return false;
		}
		return true;
	}

	bool closeSession(Session* s) {
		if(!s->closeSessionImp())
			return false;

		sessions.removeAll(s);
		delete s;
		return true;
	}

	bool sessionRequestClose(Session* s) {
		if(!sessions.contains(s))
			return false;
		if(!closeSession(s))
			return false;
		if(sessions.empty()) {
			if(sessions_not_ready.empty())
				Splash::instance()->show();
		} else {
			//auto w=sessions.front();
			//raise w
		}
		return true;
	}

	bool quitTracer() {
		switch(state) {
			case TracerState::Invalid:
				return false;
			case TracerState::Restricted:
			case TracerState::Ready:
				break;
		}

		while(!sessions.empty()) {
			if(!closeSession(sessions.front()))
				return false;
		}

		if(!sessions_not_ready.empty())
			return false;

		if(CacheThread::instance()->isRunning()) {
			CacheThread::instance()->stop();
			if(!CacheThread::instance()->wait())
				CacheThread::instance()->terminate();
		}

		tracer->quit();
		return true;
	}

	void updateActions() {
		switch(state) {
			case TracerState::Invalid:
				actions[SESSION_NEW]->setEnabled(false);
				actions[SESSION_OPEN]->setEnabled(false);
				recentMenu->setEnabled(false);
				recentGroup->setEnabled(false);
				//recoverMenu.setEnabled(false);
				//recoverGroup->setEnabled(false);
				actions[TRACER_SETTINGS]->setEnabled(false);
				actions[TRACER_QUIT]->setEnabled(false);
				actions[TRACER_MANUAL]->setEnabled(false);
				actions[TRACER_ABOUT]->setEnabled(false);
				break;
			case TracerState::Restricted:
				actions[SESSION_NEW]->setEnabled(false);
				actions[SESSION_OPEN]->setEnabled(false);
				recentMenu->setEnabled(recentFiles.size()>0);
				recentGroup->setEnabled(false);
				//recoverMenu.setEnabled(>0);
				//recoverGroup->setEnabled(false);
				actions[TRACER_SETTINGS]->setEnabled(true);
				actions[TRACER_QUIT]->setEnabled(true);
				actions[TRACER_MANUAL]->setEnabled(true);
				actions[TRACER_ABOUT]->setEnabled(true);
				break;
			case TracerState::Ready:
				actions[SESSION_NEW]->setEnabled(true);
				actions[SESSION_OPEN]->setEnabled(true);
				recentMenu->setEnabled(recentFiles.size()>0);
				recentGroup->setEnabled(true);
				//recoverMenu.setEnabled(>0);
				//recoverGroup->setEnabled(true);
				actions[TRACER_SETTINGS]->setEnabled(true);
				actions[TRACER_QUIT]->setEnabled(true);
				actions[TRACER_MANUAL]->setEnabled(true);
				actions[TRACER_ABOUT]->setEnabled(true);
				break;
		}
	}

	void setState(TracerState s) {
		state=s;
		updateActions();
	}

	bool handleCommandLine() {
		for(auto& f: toOpen) {
			auto swapFileName=pathOfSwapFile(f);
			QFile swapFile{swapFileName};
			bool recover=false;
			if(swapFile.exists()) {
				QMessageBox mbox(QMessageBox::Question, "Recover", "There is an auto-saved      version. Recover unsaved content? (Choosing \"No\" will remove it.)", QMessageBox::Yes|QMessageBox::No, nullptr);
				mbox.setDefaultButton(QMessageBox::Yes);
				auto res=mbox.exec();
				if(res==QMessageBox::No) {
					swapFile.remove();
				} else {
					recover=true;
				}
			}

			Session* s{nullptr};
			try {
				if(recover) {
					s=new Session{f, swapFileName};
					// XXX ui.recentMenu.open(f);
				} else {
					s=new Session{f};
					// XXX ui.recentMenu.open(f);
				}
				sessions_not_ready.push_front(s);
				QObject::connect(s, &Session::stateChanged, tracer, &Tracer::sessionStateChanged);
				QObject::connect(s, &Session::requestCloseSession, tracer, &Tracer::sessionRequestClose, Qt::QueuedConnection);
				QObject::connect(s, &Session::sessionReady, tracer, &Tracer::sessionReady);
				s->show();
			} catch(const std::exception& e) {
				showWarning("Failed to open session", e.what(), s);
				sessions_not_ready.removeAll(s);
				delete s;
				return false;
			}
		}

		LoadThreadInput loc{};
		for(int i=0; i<9; i++)
			loc.direction[i]=i/3==i%3?1:0;
		for(int i=0; i<3; i++)
			loc.origin[0]=0;
		for(auto& l: toCreate) {
			loc.location=l;
			if(!CreateSessionDialog::getLocation(nullptr, &loc.location, &loc.origin, &loc.direction))
				return false;

			auto swapfn=pathOfSwapFile(loc.location);
			QFile swapf{swapfn};
			bool recover=false;
			if(swapf.exists()) {
				QMessageBox mbox(QMessageBox::Question, "Recover", "There is an auto-saved version. Recover unsaved content? (Choosing \"No\" will remove it.)", QMessageBox::Yes|QMessageBox::No, nullptr);
				mbox.setDefaultButton(QMessageBox::Yes);
				auto res=mbox.exec();
				if(res==QMessageBox::No) {
					swapf.remove();
				} else {
					recover=true;
				}
			}

			Session* s{nullptr};
			try {
				if(recover) {
					s=new Session{QString{}, swapfn};
				} else {
					s=new Session{loc};
				}
				sessions_not_ready.push_front(s);
				QObject::connect(s, &Session::stateChanged, tracer, &Tracer::sessionStateChanged);
				QObject::connect(s, &Session::requestCloseSession, tracer, &Tracer::sessionRequestClose, Qt::QueuedConnection);
				QObject::connect(s, &Session::sessionReady, tracer, &Tracer::sessionReady);
				s->show();
				//s->showStatus("Session created.", 2000);
			} catch(const std::exception& e) {
				showWarning("Failed to create session", e.what(), s);
				sessions_not_ready.removeAll(s);
				delete s;
				return false;
			}
		}

		for(auto& f: toRecover) {

			auto swapFileName=pathOfSwapFile(f);
			Session* s{nullptr};
			try {
				s=new Session{f, swapFileName};
				// XXX ui.recentMenu.open(f);
				sessions_not_ready.push_front(s);
				QObject::connect(s, &Session::stateChanged, tracer, &Tracer::sessionStateChanged);
				QObject::connect(s, &Session::requestCloseSession, tracer, &Tracer::sessionRequestClose, Qt::QueuedConnection);
				QObject::connect(s, &Session::sessionReady, tracer, &Tracer::sessionReady);
				s->show();
				//s->showStatus("Session opened.", 2000);
			} catch(const std::exception& e) {
				showWarning("Failed to open session", e.what(), s);
				sessions_not_ready.removeAll(s);
				delete s;
				return false;
			}
		}
		return true;
	}

	bool setActiveSession(Session* s) {
		if(!sessions.contains(s))
			return false;
		if(sessions.front()==s)
			return true;
		sessions.front()->detachOptionsDlg();
		sessions.removeAll(s);
		sessions.push_front(s);
		sessions.front()->attachOptionsDlg();
		OptionsDlg::instance()->setState(sessions.front()->state());
		return true;
	}
	bool sessionStateChanged(Session* s, SessionState ss) {
		if(!sessions.empty() && s==sessions.front()) {
			OptionsDlg::instance()->setState(ss);
		}
		return true;
	}
	bool sessionReady(Session* s) {
		if(!sessions_not_ready.contains(s))
			return false;
		sessions_not_ready.removeAll(s);

		if(!sessions.empty()) {
			sessions.front()->detachOptionsDlg();
		} else {
			Splash::instance()->hide();
		}
		sessions.push_front(s);

		sessions.front()->attachOptionsDlg();
		OptionsDlg::instance()->setState(sessions.front()->state());

		return true;
	}
	bool sessionFailure(Session* s) {
		if(!sessions_not_ready.contains(s))
			return false;

		sessions_not_ready.removeAll(s);
		delete s;

			setState(TracerState::Restricted);

		return true;
	}
};

Tracer::Tracer(int& argc, char** argv):
	QApplication{argc, argv}, priv{new TracerPriv{this}}
{
	priv->loadOptions();
	priv->setupActions();
	priv->recentInit();

	priv->logfile.open((pathOfFnt()+"/log").toLocal8Bit().constData(), std::ios_base::app);
	if(!priv->logfile)
		printMessage("Failed to open logfile.");

	setWindowIcon(QIcon{":/images/generated/icon.svg"});
	setApplicationDisplayName(PACKAGE_NAME_LONG);
	setApplicationName(PACKAGE_NAME);
	setApplicationVersion(PACKAGE_VERSION);
	setOrganizationDomain(PACKAGE_DOMAIN);
	setOrganizationName(PACKAGE_ORG);

	QCommandLineParser parser;
	parser.setApplicationDescription(PACKAGE_DESCRIPTION);
	parser.addHelpOption();
	parser.addVersionOption();
	parser.addPositionalArgument("SESSION", "Session file to open.", "[SESSION]");
	parser.addOption(QCommandLineOption{{"n", "new"}, "Create a session with URL.", "URL"});
	parser.addOption(QCommandLineOption{{"r", "restore"}, "Restore an automatically save session in file SWAP.", "SWAP"});
	parser.process(*this);

	if(parser.isSet("n"))
		priv->toCreate.push_back(parser.value("n"));
	if(parser.isSet("r"))
		priv->toRecover.push_back(parser.value("r"));
	for(auto& f: parser.positionalArguments())
		priv->toOpen.push_back(f);


	connect(this, &QApplication::focusChanged, this, &Tracer::sessionFocusChanged);
}

Tracer::~Tracer() {
	priv->recentDeinit();
	priv->saveOptions();
	priv->logfile.close();
	if(!priv->logfile)
		printMessage("Failed to close logfile.");
	delete priv;
}

void Tracer::start() {
	connect(CacheThread::instance(), &CacheThread::threadError, this, &Tracer::cacheError);
	connect(CacheThread::instance(), &CacheThread::threadWarning, this, &Tracer::cacheWarning);
	connect(CacheThread::instance(), &CacheThread::requestLogin, this, &Tracer::requestLogin);
	CacheThread::instance()->start();
	priv->handleCommandLine();
	if(priv->sessions_not_ready.empty() && priv->sessions.empty())
		Splash::instance()->show();
	priv->setState(TracerState::Ready);
}

bool Tracer::notify(QObject* receiver, QEvent* event) {
	try {
#ifdef DEBUG_EVENT
		if(ofs.is_open()) {
			ofs<<"obj: "<<typeid(receiver).name()<<", event: "<<event->type()<<'\n';
			ofs.flush();
		}
#endif
		return QApplication::notify(receiver, event);
	} catch(const std::exception& e) {
		QMessageBox mbox(QMessageBox::Critical, "Critical Error", QString{"Uncaught exception: "}+=e.what(), QMessageBox::Ok);
		mbox.setDefaultButton(QMessageBox::Ok);
		mbox.exec();
		return true;
	}
}

	/* // // */


/////////////////////////

	//






void Tracer::createSession() { priv->createSession(); }
void Tracer::openSession() { priv->openSession(); }

void Tracer::openSessionRecent() { priv->openSessionRecent(sender()); }
void Tracer::clearRecentList() {
	switch(priv->state) {
		case TracerState::Invalid:
			break;
		case TracerState::Restricted:
		case TracerState::Ready:
			priv->recentMenuClear();
			break;
	}
}

void Tracer::openSessionRecover() { priv->openSessionRecover(sender()); }
void Tracer::clearRecoverList() {
	// XXX
}

void Tracer::sessionRequestClose(Session* s) { priv->sessionRequestClose(s); }
void Tracer::quitTracer() { priv->quitTracer(); }

void Tracer::showSettings() {
	switch(priv->state) {
		case TracerState::Invalid:
		return;
		case TracerState::Restricted:
		case TracerState::Ready:
		break;
	}
	OptionsDlg::instance()->show();
	OptionsDlg::instance()->activateWindow();
}
void Tracer::showManual() {
	switch(priv->state) {
		case TracerState::Invalid:
			return;
		case TracerState::Restricted:
		case TracerState::Ready:
			break;
	}
	auto par=priv->sessions.empty()?static_cast<QWidget*>(Splash::instance()):priv->sessions.front();
	openManual(par);
}
void Tracer::showAbout() {
	switch(priv->state) {
		case TracerState::Invalid:
			return;
		case TracerState::Restricted:
		case TracerState::Ready:
			break;
	}
	if(!priv->aboutDlg) {
		QWidget* p{nullptr};
		if(!priv->sessions.empty()) {
			p=priv->sessions[0];
		} else {
			p=Splash::instance();
		}
		priv->aboutDlg=new AboutDlg{p};
	}
	priv->aboutDlg->show();
}

 void Tracer::cacheWarning(const QString& msg) {
	showWarning("Cache thread warning: "+msg, nullptr);
 }

void Tracer::cacheError(const QString& msg) {
	showWarning("Error in cache thread", msg, nullptr);
	// XXX set all sessions to readonly???
	// set all sessions to restricted
}

////////////////////

void Tracer::requestLogin(const QString& _url, const QString& _usr, const QString& _pwd) {
	QString usr=_usr;
	QString pwd=_pwd;
	auto r=LoginInfoDialog::getLoginInfo(&usr, &pwd, _url);
	CacheThread::instance()->updateLogin(usr, pwd, !r);
}

const std::array<QAction*, TRACER_ACTION_NUM>& Tracer::tracerActions() const {
	return priv->actions;
}
QMenu* Tracer::recentMenu() const { return priv->recentMenu; }
QMenu* Tracer::recoverMenu() const {
	//return priv->recoverMenu;
	return priv->recentMenu;
}
Options* Tracer::options() const { return &priv->options; }

void Tracer::sessionReady(Session* s, bool e) {
	if(e) {
		priv->sessionFailure(s);
	} else {
		priv->sessionReady(s);
	}
}

LoadThreadShared* Tracer::loaderShared() const { return priv->loaderShared; }
ViewerShared* Tracer::viewerShared() const { return priv->viewerShared; }

void Tracer::sessionFocusChanged(QWidget* w0, QWidget* w1) {
	if(w1) {
		auto s=qobject_cast<Session*>(w1->window());
		if(s) {
			priv->setActiveSession(s);
		}
	}
}
void Tracer::sessionStateChanged(Session* s, SessionState ss) { priv->sessionStateChanged(s, ss); }

Tracer* Tracer::_instance{nullptr};
Tracer* Tracer::create(int& argc, char** argv) {
	if(_instance)
		throwError("Already created!");
	_instance=new Tracer{argc, argv};
	return _instance;
}

template <typename C>
inline std::ostream& operator<<(std::ostream& fs, const std::chrono::time_point<C>& tp) {
	auto tt=std::chrono::system_clock::to_time_t(tp);
	char buf[64];
	auto r=strftime(buf, 64, "%Y-%m-%d %H:%M:%S %z", localtime(&tt));
	fs.write(buf, r);
	return fs;
}

void Tracer::logMessageHelper(const std::string& s) const {
	(priv->logfile<<'['<<std::chrono::system_clock::now()<<"]: "<<s<<'\n').flush();
}

